﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace WindGoes6.Data
{
	/// <summary>
	/// 一个基于字符串的用于简单的配置参数记录的的字典类。
	/// 提供了基于<c>(key, value)</c> 方式的读写方法，同时数据也可以向文件中做持久化。
	/// 在使用的时候需要注意以下几点:
	/// * 如果某key出现多次，取第一次出现的值，忽略后面的值。
	/// * key 是一个左右无空格、长度大于0且不包括等于号(=)的字符串。
	/// * value 可以是任意字符串，包括长度为0的字符串和null。
	/// * 以#或//头的行会被当成注释而会忽略
	/// * value中的, /n 会被替换为一个长度为10的特殊字符串，从而保证在换行时不受影响。
	/// * 一行的开头的空格会被忽略。
	/// * 默认使用 UTF-8进行编码。
	/// </summary>
	public class Configuration
	{
		// 换行符 /n
		string newline = "|x?=*.)}0|";
		// 空值 null
		string nullstr = "|ul=n*.&-|";

		/// <summary>
		/// 读写文件时的编码，默认为UTF8
		/// </summary>
		public Encoding MapEncoding { get; set; } = Encoding.Default;

		/// <summary>
		/// 数据存入的核心变量，所有的配置数据都写于此。
		/// </summary>
		Dictionary<string, string> map = new Dictionary<string, string>();

		/// <summary>
		/// 无参数的构造函数。
		/// </summary>
		public Configuration() { }

		/// <summary>
		/// 根据给定文件加载数据。
		/// </summary>
		/// <param name="filepath">给定文件。</param>
		public Configuration(string filepath)
		{
			Load(filepath);
		}

		/// <summary>
		/// 根据给定文件加载数据。
		/// </summary>
		/// <param name="filepath">给定文件。</param>
		public Configuration(string filepath, Encoding encoding)
		{
			this.MapEncoding = encoding;
			Load(filepath);
		}


		/// <summary>
		/// 从文件中读取配置信息。
		/// </summary>
		/// <param name="file">需要加载的文件</param>
		/// <returns>返回true表示加载成功，否则加载失败。</returns>
		public bool Load(String file)
		{
			if (!File.Exists(file))
				return false;

			map = new Dictionary<string, string>();
			string[] lines = File.ReadAllLines(file, MapEncoding);
			foreach (string line in lines)
			{
				// 本行为空则返回
				string s = line.TrimStart();
				if (s.Length == 0)
					continue;

				// 注释的行忽略
				if (s.StartsWith("//") || s.StartsWith("#"))
					continue;

				// 用=分隔，如果以=开头或者不存在 则返回 
				int p = s.IndexOf('=');
				if (p <= 0)
					continue;

				// 键和值左右不能有空格
				string key = s.Substring(0, p).Trim();

				// value 没有限制，要么为空值，要么换行会被替换掉。
				string value = s == nullstr ? null : s.Substring(p + 1).Replace(newline, "\n");

				// 如果值不存在则添加
				if (!map.Keys.Contains(key))
					map.Add(key, value);
			}

			return true;
		}

		/// <summary>
		/// 将所有内容保存至的文件，如果文件不存在则新建，如果已经存在 ，则覆盖。
		/// </summary>
		/// <param name="file">保存文件的路径。</param>
		/// <returns></returns>
		public bool Save(String file)
		{
			StringBuilder sb = new StringBuilder();
			foreach (var key in map.Keys)
				sb.AppendLine(key + "=" + map[key].Replace("\n", newline));
			File.WriteAllText(file, sb.ToString(), MapEncoding);
			return true;
		}

		/// <summary>
		/// 返回所有的键值。
		/// </summary>
		/// <returns></returns>
		public List<string> GetAllKeys()
		{
			List<string> keys = new List<string>();
			if (map != null)
				foreach (var key in map.Keys)
					keys.Add(key);

			return keys;
		}

		/// <summary>
		/// 返回指定key的String类型的值，如果不存在 则返回输入的缺省值。
		/// </summary>
		/// <param name="key">key</param>
		/// <param name="defaultValue">如果key不存在时的返回的缺省值。</param>
		/// <returns></returns>
		public String GetString(string key, string defaultValue = null)
		{
			return map.Keys.Contains(key) ? map[key] : defaultValue;
		}

		/// <summary>
		/// 返回指定key的Int32类型的值，如果不存在 则返回输入的缺省值(不写时返回int.MaxValue)。
		/// </summary>
		/// <param name="key">key</param>
		/// <param name="defaultValue">如果key不存在时的返回的缺省值。</param>
		/// <returns></returns>
		public int GetInt(string key, int defaultValue = int.MaxValue)
		{
			try
			{
				if (map.Keys.Contains(key))
					return int.Parse(map[key]);
			}
			catch { }
			return defaultValue;
		}

		/// <summary>
		/// 返回指定key的Float32类型的值，如果不存在 则返回输入的缺省值(不写时返回NaN)。
		/// </summary>
		/// <param name="key">key</param>
		/// <param name="defaultValue">如果key不存在时的返回的缺省值。</param>
		/// <returns></returns>
		public float GetFloat(string key, float defaultValue = float.NaN)
		{
			try
			{
				if (map.Keys.Contains(key))
					return float.Parse(map[key]);
			}
			catch { }
			return defaultValue;
		}


		/// <summary>
		/// 返回指定key的DateTime类型的值，如果不存在 则返回输入的缺省值。
		/// </summary>
		/// <param name="key">key</param>
		/// <param name="defaultValue">如果key不存在时的返回的缺省值。</param>
		/// <returns></returns>
		public DateTime GetDateTime(string key, DateTime defaultValue)
		{
			try
			{
				if (map.Keys.Contains(key))
					return DateTime.Parse(map[key]);
			}
			catch { }
			return defaultValue;
		}

		/// <summary>
		/// 返回指定key的DateTime类型的值，如果不存在 则返回输入的缺省值。
		/// </summary>
		/// <param name="key">key</param>
		/// <param name="defaultValue">如果key不存在时的返回的缺省值。</param>
		/// <returns></returns>
		public DateTime GetDateTimeExact(string key, string format, DateTime defaultValue)
		{
			try
			{
				if (map.Keys.Contains(key))
					return DateTime.ParseExact(map[key], format, null);
			}
			catch { }
			return defaultValue;
		}


		/// <summary>
		/// 返回指定key的Int32类型的值，如果不存在 则返回输入的缺省值(不写时返回NaN)。
		/// </summary>
		/// <param name="key">key</param>
		/// <param name="defaultValue">如果key不存在时的返回的缺省值。</param>
		/// <returns></returns>
		public double GetDouble(string key, double defaultValue = double.NaN)
		{
			try
			{
				if (map.Keys.Contains(key))
					return double.Parse(map[key]);
			}
			catch { }
			return defaultValue;
		}


		/// <summary>
		/// 显示map中的所有内容， 用于测试。
		/// </summary>
		public void Show()
		{
			foreach (var key in map.Keys)
			{
				Console.WriteLine("[{0}][=][{1}]", key, map[key]);
			}
		}

		/// <summary>
		/// 向指定的key值写入指定值。如果key不存在会创建，否则会进行更新。
		/// </summary>
		/// <param name="key">Key</param>
		/// <param name="value">待写入的值。</param>
		public void SetValue(string key, string value)
		{
			string ev = value?.Replace("\n", "\\n");
			if (map.Keys.Contains(key))
				map[key] = value;
			else
				map.Add(key, value);
		}
	}
}


